package com.leyou.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.client.item.ItemClient;
import com.leyou.common.pojo.PageResult;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.HighlightUtils;
import com.leyou.common.utils.JsonUtils;
import com.leyou.item.dto.SpuDTO;
import com.leyou.item.pojo.*;
import com.leyou.repository.SearchRespository;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.dto.SearchResult;
import com.leyou.search.pojo.Goods;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 搜索业务
 */
@Service
public class SearchService {
    @Autowired
    private SearchRespository searchRespository;
    @Autowired
    private ItemClient itemClient;
    @Autowired
    private ElasticsearchTemplate esTemplate;

    /**
     * 导入数据方法：从MySQL导入数据到Elasticsearch
     */
    public void importData(){

        //当前页码
        int page = 1;
        int rows = 100;

        //总页数
        long totalPage = 1;
        //1.分页查询Spu数据（SpuDTO）
        do {
            PageResult<SpuDTO> pageResult = itemClient.findSpuByPage(page, rows, null, true);//注意：只有上架的商品才能导入ES中

            //2.把所有SpuDTO转换为Goods对象
            List<SpuDTO> spuDTOList = pageResult.getItems();

            List<Goods> goodsList = spuDTOList.stream().map(this::buildGoods).collect(Collectors.toList());

            //3.把Goods对象存入Elasticsearch
            searchRespository.saveAll(goodsList);

            page++;//每次增加1
            totalPage = pageResult.getTotalPage();//获取总页数
        }while (page<=totalPage);
    }

    /**
     * 把一个SpuDTO转换为一个Goods
     */
    public Goods buildGoods(SpuDTO spuDTO){
        Goods goods = new Goods();

        //1.处理skus属性
        //1.1 根据spuId查询所有Sku集合
        List<Sku> skus = itemClient.findSkusBySpuId(spuDTO.getId());


        //1.2 定义新的List集合存放所有需要的Sku数据
        List<Map<String,Object>> skuMapList = new ArrayList<>();
        //1.3 遍历skus
        skus.forEach(sku -> {
            //封装需要的Sku的字段数据
            Map<String,Object> skuMap = new HashMap<>();
            skuMap.put("id",sku.getId());
            skuMap.put("image",sku.getImages());
            skuMap.put("price",sku.getPrice());
            skuMap.put("title",sku.getTitle());
            skuMap.put("ownSpec",sku.getOwnSpec());

            skuMapList.add(skuMap);
        });
        //1.4 把List集合转换json字符串
        String skuJson = JsonUtils.toString(skuMapList);

        //2. 处理all属性 : spu的name和subTitle，所有Sku的title
        String all = spuDTO.getName()+spuDTO.getSubTitle()+skus.stream().map(Sku::getTitle).collect(Collectors.joining(""));

        //3. 处理price
        Set<Long> price = skus.stream().map(Sku::getPrice).collect(Collectors.toSet());

        //4.处理specs属性
        /**
         * 格式：
         *  {
         *      "CPU品牌":"骁龙"
         *      "CPU核数":"4核"
         *      ....
         *  }
         *
         *  上面的格式中
         *     key: 来源于规格参数表名称(tb_spec_param的name字段)
         *     value：来源于Spu详情表的通用或特有参数字段（tb_spu_detail的generic_spec或special_spec）
         *
         */
        //4.1 查询当前分类下的用于搜索的规格参数
        List<SpecParam> specParams = itemClient.findSpecParams(null, spuDTO.getCid3(), true);

        //根据spuId查询SpuDetail对象
        SpuDetail spuDetail = itemClient.findSpuDetialBySpuId(spuDTO.getId());

        //定义用于存储所有用于搜索的规格参数数据
        Map<String,Object> specsParamMap = new HashMap<>();
        specParams.forEach(specParam -> {
            //4.2 设置specs中的key
            String key = specParam.getName();
            //4.3 设置specs中的value
            Object value = null;

            //1）判断当前规格参数是否属于通用规格参数
            if(specParam.getGeneric()){
                //通用规格参数（只有一个值的）
                String genericSpec = spuDetail.getGenericSpec();

                //把genericSpec的json字符串转换Map集合
                Map<Long, Object> genericSpecMap = JsonUtils.toMap(genericSpec, Long.class, Object.class);

                // 根据规格参数id查询genericSpec的值
                value = genericSpecMap.get(specParam.getId());
            }else{
                //特有参数（可能有多个值的）
                String specialSpec = spuDetail.getSpecialSpec();

                //把specialSpec转换为Map集合
                Map<Long,List<Object>> specialSpecMap = JsonUtils.nativeRead(specialSpec, new TypeReference<Map<Long, List<Object>>>() {});

                //根据规格参数id查询specialSpec的值
                value = specialSpecMap.get(specParam.getId());

            }


            //判断该规格参数是否为数字，如果为数字，把该具体值转换为一个区间范围
            if(specParam.getNumeric()){
                value = chooseSegment(value,specParam);
            }

            //4.4 把参数存入Map集合
            specsParamMap.put(key,value);
        });

        goods.setId(spuDTO.getId());
        goods.setSpuName(spuDTO.getName());
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setAll(all);
        goods.setSkus(skuJson);
        goods.setCategoryId(spuDTO.getCid3());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setSpecs(specsParamMap);
        goods.setPrice(price);
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        return goods;
    }

    private String chooseSegment(Object value, SpecParam p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    public SearchResult<GoodsDTO> goodsSearch(SearchRequest searchRequest) {
        //1.创建SearchResult对象
        SearchResult<GoodsDTO> searchResult = new SearchResult<GoodsDTO>();

        //2.封装SearchResult对象
        //itemQueryPage: 分页查询商品列表
        //2.1 分页查询商品列表
        PageResult<Goods> pageResult = itemQueryPage(searchRequest);
        //2.2 取出Goods集合
        List<Goods> goodsList = pageResult.getItems();
        //2.3 把Goods集合转换为GoodsDTO集合
        List<GoodsDTO> goodsDTOList = BeanHelper.copyWithCollection(goodsList, GoodsDTO.class);

        //2.4 查询搜索过滤条件
        Map<String,Object> filterConditions = filterConditionsQuery(searchRequest);

        searchResult.setTotalPage(pageResult.getTotalPage());
        searchResult.setTotal(pageResult.getTotal());
        searchResult.setItems(goodsDTOList);
        searchResult.setFilterConditions(filterConditions);
        //3.返回SearchResult对象
        return searchResult;
    }

    /**
     * 查询搜索过滤条件
     * @param searchRequest
     * @return
     */
    public Map<String,Object> filterConditionsQuery(SearchRequest searchRequest) {
        //定义Map集合，用于存放所有搜索过滤条件
        Map<String,Object> filterConditions = new LinkedHashMap<>();//注意：过滤条件是有序的

        //1.创建本地查询构造器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //2.往本地查询构造封装条件
        //2.1 创建布尔组合查询构造器
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //2.2 往布尔查询构造器添加条件
        boolQueryBuilder.must(QueryBuilders.multiMatchQuery(searchRequest.getKey(),"all","spuName"));
        //2.3 把布尔查询构造器放入本地查询构造器
        queryBuilder.withQuery(boolQueryBuilder);
        //2.4 设置结果过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""},null));
        //2.5 添加 分类 和 品牌 聚合条件
        //1）定义聚合别名
        String categoryAggName = "categoryAgg";
        String brandAggName = "brandAgg";

        //2）添加分类聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("categoryId"));
        //3）添加品牌聚合
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));


        //添加规格参数的过滤条件筛选
        addFilterParams(searchRequest, boolQueryBuilder);

        //4）执行查询，获取聚合结果
        AggregatedPage<Goods> categoryBrandAggPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        //取出所有集合结果
        Aggregations categoryBrandAggs = categoryBrandAggPage.getAggregations();

        //一、取出分类的桶数据
        Terms categoryTerms = categoryBrandAggs.get(categoryAggName);
        //1）获取分类所有ID
        List<Long> categoryIds = categoryTerms
                .getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber) // 把Bucket中的key以Number类型返回
                .map(Number::longValue)  //把上一步Number值，调用Number的longValue方法，转换为Long类型
                .collect(Collectors.toList()); //把map方法处理结果收集为List集合

        //2）根据分类的ID集合 获取 分类的对象集合
        List<Category> categories = itemClient.findCategoriesByIds(categoryIds);
        //3）把分类聚合存入Map集合
        filterConditions.put("分类",categories);

        //二、取出品牌的桶数据
        Terms brandTerms = categoryBrandAggs.get(brandAggName);
        //1）获取品牌所有ID
        List<Long> brandIds = brandTerms
                .getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber) // 把Bucket中的key以Number类型返回
                .map(Number::longValue)  //把上一步Number值，调用Number的longValue方法，转换为Long类型
                .collect(Collectors.toList()); //把map方法处理结果收集为List集合

        //2）根据品牌的ID集合 获取 品牌的对象集合
        List<Brand> brands = itemClient.findBrandsByIds(brandIds);
        //3）把品牌聚合存入Map集合
        filterConditions.put("品牌",brands);


        //添加动态规格参数的聚合条件
        //1）遍历所有分类id，分类查询用于搜索的规格参数
        categoryIds.forEach(categoryId->{
            //查询用于搜索的规格参数
            List<SpecParam> specParams = itemClient.findSpecParams(null, categoryId, true);

            //把上面查询出来的规格参数逐个放入聚合条件中
            specParams.forEach(specParam -> {
                queryBuilder.addAggregation(AggregationBuilders.terms(specParam.getName()).field("specs."+specParam.getName()+".keyword"));
            });

            //执行聚合查询
            AggregatedPage<Goods> specParamsAggPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

            //取出所有规格参数聚合结果
            Aggregations specParamAggs = specParamsAggPage.getAggregations();

            //遍历聚合结果，封装到Map集合
            specParams.forEach(specParam -> {
                //获取规格参数名称
                String key = specParam.getName();

                //取出规格参数每个聚合结果
                Terms specParamTerms = specParamAggs.get(key);

                //从每个聚合结果汇总取出key值，并封装为List集合
                List<String> specParamList= specParamTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());

                //把每个规格参数的聚合结果存入Map集合
                filterConditions.put(key,specParamList);
            });

        });


        return filterConditions;
    }

    /**
     * 分页查询商品列表
     * @param searchRequest
     * @return
     */
    public PageResult<Goods> itemQueryPage(SearchRequest searchRequest) {
        //1.创建本地查询构造器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        //2.往本地查询构造封装条件
        //2.1 创建布尔组合查询构造器
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //2.2 往布尔查询构造器添加条件
        boolQueryBuilder.must(QueryBuilders.multiMatchQuery(searchRequest.getKey(),"all","spuName"));
        //2.3 把布尔查询构造器放入本地查询构造器
        queryBuilder.withQuery(boolQueryBuilder);
        //2.4 设置结果过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","spuName","subTitle","skus"},null));
        //2.5 设置分页参数
        queryBuilder.withPageable(PageRequest.of(searchRequest.getPage()-1,searchRequest.getSize()));
        //2.6 把spuName作为高亮字段，并添加高亮格式
        HighlightUtils.highlightField(queryBuilder,"spuName");

        //添加规格参数的过滤条件筛选
        addFilterParams(searchRequest, boolQueryBuilder);


        //3.执行查询，获取结果
        Page<Goods> pageBean = esTemplate.queryForPage(queryBuilder.build(),Goods.class,HighlightUtils.highlightBody(Goods.class,"spuName"));

        //4.处理结果，并返回
        PageResult<Goods> pageResult = new PageResult<>(pageBean.getTotalElements(),Long.valueOf(pageBean.getTotalPages()),pageBean.getContent());
        return pageResult;
    }

    /**
     * 添加规格参数的过滤条件
     * @param searchRequest
     * @param boolQueryBuilder
     */
    private void addFilterParams(SearchRequest searchRequest, BoolQueryBuilder boolQueryBuilder) {
        //1.取出页面传递所有规格参数的过滤条件
        Map<String, Object> filterParams = searchRequest.getFilterParams();
        if(filterParams!=null){
            filterParams.entrySet().forEach(entry->{
                String key = entry.getKey();
                Object value = entry.getValue();

                //处理key
                //除了分类和品牌这个两个参数以为，都需要拼接key
                if(key.equals("分类")){
                    key = "categoryId";
                }else if(key.equals("品牌")){
                    key = "brandId";
                }else{
                    key = "specs."+key+".keyword";
                }

                boolQueryBuilder.filter(QueryBuilders.termQuery(key,value));
            });
        }
    }

    public List<GoodsDTO> pageChange(SearchRequest searchRequest) {
        //1.查询商品分页数据
        PageResult<Goods> pageResult = this.itemQueryPage(searchRequest);
        //2.取出商品列表
        List<Goods> goodsList = pageResult.getItems();
        //3.转换为GoodDTO对象
        return BeanHelper.copyWithCollection(goodsList,GoodsDTO.class);
    }

    public void indexCreate(Long spuId) {
        SpuDTO spuDTO = itemClient.findSpuById(spuId);
        Goods goods = this.buildGoods(spuDTO);
        searchRespository.save(goods);
    }

    public void indexDelete(Long spuId) {
        searchRespository.deleteById(spuId);
    }
}
